#include <windows.h>
#include <commctrl.h>
#include <shellapi.h>

#include "window.h"
#include "mmbar.h"
#include "monitors.h"
#include "support.h"
#include "events.h"
#include "config.h"

#define ClassName "MonMan"
#define HelpFile "MonMan.txt"

HWND main_window = 0;
HMENU menu;
//UINT shell_hook_msg;

enum { CmdExit, CmdAbout, CmdHelp };

static void register_class(void);
static void create_main_window(void);

static BOOL CALLBACK release_window_proc(HWND, LPARAM);
static BOOL CALLBACK track_window_proc(HWND wnd, LPARAM lp);
static LRESULT CALLBACK wnd_proc(HWND, UINT, WPARAM, LPARAM);

void init_windows(void)
{
	INITCOMMONCONTROLSEX ctrls;
	
	ctrls.dwSize = sizeof(INITCOMMONCONTROLSEX);
	ctrls.dwICC = ICC_COOL_CLASSES | ICC_BAR_CLASSES;
	
	InitCommonControlsEx(&ctrls);
	
	register_class();
	create_main_window();
	
	menu = CreatePopupMenu();
	
	AppendMenu(menu, MF_STRING, CmdAbout, "&About MonMan...");
	AppendMenu(menu, MF_STRING, CmdHelp, "&Help...");
	AppendMenu(menu, MF_SEPARATOR, 0, 0);
	AppendMenu(menu, MF_STRING, CmdExit, "E&xit");
	
	//shell_hook_msg = RegisterWindowMessage(TEXT("SHELLHOOK"));
	//if(!shell_hook_msg) fatal("Message error");
}

void popup_menu(void)
{
	POINT cursor_pos;
	GetCursorPos(&cursor_pos);
	
	TrackPopupMenuEx(
		menu,
		GetSystemMetrics(SM_MENUDROPALIGNMENT),
		cursor_pos.x,
		cursor_pos.y,
		main_window,
		0
	);
}

void track_windows(void)
{
	EnumWindows(&track_window_proc, 0);
}

void process_messages(void)
{
	MSG msg;
	
	while(GetMessage(&msg, 0, 0, 0)) 
	{
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
}

void link_to_monitor(HWND wnd, struct Monitor *mon)
{
	SetWindowLongPtr(wnd, GWLP_USERDATA, (LONG_PTR)mon);
}

struct Monitor *get_linked_monitor(HWND wnd)
{
	struct Monitor *mon = 0;
	
	while(!mon)
	{
		mon = (struct Monitor*) GetWindowLongPtr(wnd, GWLP_USERDATA);
		wnd = GetParent(wnd);
		if(!wnd) break;
	}
		
	return mon;
}

struct Monitor *find_monitor(HWND wnd)
{
	HMONITOR hmon = MonitorFromWindow(wnd, MONITOR_DEFAULTTONULL);
	if(hmon)
		return from_hmonitor(hmon);
	else
		return 0;
}

struct Monitor *get_cursor_monitor(void)
{
	POINT cursor_pos;
	HMONITOR hmon;
	
	GetCursorPos(&cursor_pos);
	
	hmon = MonitorFromPoint(cursor_pos, MONITOR_DEFAULTTONULL);
	if(hmon)
		return from_hmonitor(hmon);
	else
		return 0;
}

char *alloc_title(HWND wnd)
{
	int sz = GetWindowTextLength(wnd) + 1;
	char *buf = malloc(sz);
	GetWindowText(wnd, buf, sz);
	return buf;
}

void free_windows(void)
{
	EnumWindows(&release_window_proc, 0);
	DestroyWindow(main_window);
}

static void register_class(void)
{
	WNDCLASSEX wc;
	ZeroMemory(&wc, sizeof(WNDCLASSEX));
	
	wc.cbSize = sizeof(WNDCLASSEX);
	wc.lpfnWndProc = wnd_proc;
	wc.hInstance = GetModuleHandle(0);
	wc.hCursor = LoadCursor(0, IDC_ARROW);
	wc.hbrBackground = (HBRUSH)(COLOR_WINDOW);
	wc.lpszClassName = ClassName;
	
	RegisterClassEx(&wc);
}

static void create_main_window(void)
{
	NOTIFYICONDATA nid;
	HICON icon;
	
	main_window = CreateWindowEx(
		0,
		ClassName,
		ClassName,
		WS_OVERLAPPEDWINDOW,
		0, 0,
		100, 100,
		HWND_DESKTOP,
		0,
		GetModuleHandle(0),
		0
	);
	if(!main_window) fatal("Can't create main window");
	
	icon = LoadIcon(GetModuleHandle(0), MAKEINTRESOURCE(2));
	
	nid.cbSize = sizeof(NOTIFYICONDATA);
	nid.hWnd = main_window;
	nid.uID = 0;
	nid.uFlags = NIF_ICON | NIF_TIP | NIF_MESSAGE;
	nid.hIcon = icon;
	nid.uCallbackMessage = TrayMsg;
	
	strcpy(nid.szTip, "MonMan");
	
	Shell_NotifyIcon(NIM_ADD, &nid);
}

static void on_destroy(void)
{
	NOTIFYICONDATA nid;
	
	nid.cbSize = sizeof(NOTIFYICONDATA);
	nid.hWnd = main_window;
	nid.uID = 0;
	nid.uFlags = 0;
	
	Shell_NotifyIcon(NIM_DELETE, &nid);
}

static void add_to_toolbar(HWND wnd, struct Monitor *mon)
{
	//if(IsIconic(wnd))
	//	ShowWindow(wnd, SW_HIDE);
	
	if(mon->tbar)
		add_button(mon->tbar, wnd);
	else
		add_to_taskbar(wnd);
}

static void remove_from_toolbar(HWND wnd, struct Monitor *mon)
{
	remove_button(mon->tbar, wnd);
}

static void remove_from_toolbars(HWND wnd)
{
	int i;
	for(i = 0; i < num_monitors; ++i)
		remove_from_toolbar(wnd, monitors + i);
}

void track_window(HWND wnd)
{
	struct Monitor *mon;
	int i;
	
	if(should_ignore(wnd))
		return;
	
	mon = find_monitor(wnd);
	
	if(!is_primary(mon))
		remove_from_taskbar(wnd);
	
	for(i = 0; i < num_monitors; ++i)
	{
		if(monitors + i == mon)
		{
			add_to_toolbar(wnd, mon);
			
			//if(is_primary(mon))
				//add_to_taskbar(wnd);
		}
		else
		{
			remove_from_toolbar(wnd, monitors + i);
			
			//if(is_primary(monitors + i))
			//	remove_from_taskbar(wnd);
		}
	}
}

int should_ignore(HWND wnd)
{
	static char class_name[64];
	LONG exs;
	HWND parent;
	
	if(wnd == main_window) 
		return 1;
	
	if(!IsWindowVisible(wnd))
		return 1;
	
	exs = GetWindowLong(wnd, GWL_EXSTYLE);
	
	if(exs & WS_EX_TOOLWINDOW)
		return 1;
	
	parent = (HWND)GetWindowLong(wnd, GWL_HWNDPARENT);
	
	if(parent)
		if(!(exs & WS_EX_APPWINDOW))
			return 1;
		
	return 0;
}

void untrack_window(HWND wnd)
{
	struct Monitor *mon;
	
	// wnd may have been destroyed already
	if(IsWindow(wnd))
	{
		if(should_ignore(wnd))
			return;
	
		add_to_taskbar(wnd);
	}
	
	remove_from_toolbars(wnd);
}

static void pop_menu(void)
{
	POINT mouse;
	
	GetCursorPos(&mouse);
	SetForegroundWindow(main_window);
	
	TrackPopupMenu(menu, TPM_RIGHTALIGN | TPM_BOTTOMALIGN, mouse.x, mouse.y, 0, main_window, 0);
	
	PostMessage(main_window, WM_NULL, 0, 0);
}

static void about(void)
{
	HWND cursor_bar = get_cursor_monitor()->bar;
	
	MessageBox(
		cursor_bar,
		"MonMan " VERSTR
		"\r\nReleased under the terms of the GNU General Public License v3.0"
		"\r\nSee LICENSE.txt for details"
		"\r\n" __DATE__,
		"About MonMan",
		MB_ICONINFORMATION
	);
}

static void help(void)
{
	/*
		TODO: ShellExecuteEx seems to ignore hMonitor parameter and always use 
		the primary monitor.  The whole point of this project is to do 
		Microsoft's job for them so fix this at some point i guess.
	*/
	struct Monitor *cursor_monitor = get_cursor_monitor();
	HWND cursor_bar = cursor_monitor->bar;
	int ok = GetFileAttributes(HelpFile) != INVALID_FILE_ATTRIBUTES;
	
	if(ok) {
		SHELLEXECUTEINFO a = {0};
		a.cbSize = sizeof(a);
		a.fMask = SEE_MASK_FLAG_NO_UI | SEE_MASK_HMONITOR;
		a.hMonitor = cursor_monitor->hmon;
		a.hwnd = cursor_bar;
		a.lpVerb = "open";
		a.lpFile = HelpFile;
		a.nShow = SW_SHOW;
		
		ok = ShellExecuteEx(&a) == TRUE;
	}
	
	if(!ok) 
		MessageBox(
			cursor_bar,
			"Unable to open help",
			"MonMan",
			MB_ICONERROR
		);
}

static void get_min_rect(HWND wnd, LPRECT rect)
{
	struct Monitor *mon = find_monitor(wnd);
	RECT tb_rect;
	RECT but_rect;
	int index;
	
	if(!mon)
		return;
	
	if(!mon->tbar)
		return;
	
	index = by_data(mon->tbar, wnd);
	if(index == -1)
		return;
	
	GetWindowRect(mon->tbar, &tb_rect);
	SendMessage(mon->tbar, TB_GETRECT, index, (LPARAM)&but_rect);
	
	rect->left = tb_rect.left + but_rect.left;
	rect->top = tb_rect.top + but_rect.top;
	rect->right = tb_rect.left + but_rect.right;
	rect->bottom = tb_rect.top + but_rect.bottom;
}

/*static void handle_shell_msg(WPARAM wp, LPARAM lp)
{
}*/

static BOOL CALLBACK release_window_proc(HWND wnd, LPARAM lp)
{
	if(!should_ignore(wnd))
		add_to_taskbar(wnd);
	
	return TRUE;
}

static BOOL CALLBACK track_window_proc(HWND wnd, LPARAM lp)
{
	track_window(wnd);
	return TRUE;
}

static LRESULT CALLBACK wnd_proc(HWND wnd, UINT msg, WPARAM wp, LPARAM lp)
{
	switch(msg)
	{
		case WM_CLOSE:
			DestroyWindow(wnd);
			break;
			
		case WM_DESTROY:
			on_destroy();
			break;
			
		case WM_THEMECHANGED:
		case WM_DISPLAYCHANGE:
			init_monitors();
			track_windows();
			break;
			
		case WM_COMMAND:
			switch(LOWORD(wp))
			{
				case CmdExit:
					PostQuitMessage(0);
					break;
					
				case CmdAbout:
					about();
					break;
					
				case CmdHelp:
					help();
					break;
			}
			break;
			
		case TrackMsg:
			track_window((HWND)wp);
			break;
		
		case UntrackMsg:
			untrack_window((HWND)wp);
			break;
			
		case TrayMsg:
			if(lp == WM_LBUTTONDOWN || lp == WM_RBUTTONDOWN)
				pop_menu();
			break;
			
		case ActivateMsg:
		{
			HWND wnd = (HWND)wp;
			struct Monitor *wmon;
			int i;
			
			if(wnd && !should_ignore(wnd))
				wmon = find_monitor(wnd);
			else
				wmon = 0;
			
			for(i = 0; i < num_monitors; ++i)
			{
				struct Monitor *mon = monitors + i;
				
				if(!mon->tbar)
					continue;
				
				if(mon == wmon)
				{
					// The active window is on this monitor, push in its
					// button.
					activate(mon->tbar, wnd);
				}
				else
				{
					// The active window isn't on this monitor, so pop
					// out all buttons
					activate(mon->tbar, 0);
				}
			}
			
			// Best to call track_window as often as possible to make
			// sure all windows stay tracked.
			if(wnd)
				track_window(wnd);
			
			break;
		}
		
		case GetMinRectMsg:
		{
			RECT rect;
			HWND wnd = (HWND)wp;
			
			get_min_rect(wnd, &rect);
			
			switch(lp)
			{
				case 0: return rect.left;
				case 1: return rect.top;
				case 2: return rect.right;
				case 3: return rect.bottom;
			}
		}
		
		case MinimizeMsg:
		{
			HWND wnd = (HWND)wp;
			struct Monitor *mon = find_monitor(wnd);
			
			//ShowWindow(wnd, SW_MINIMIZE);
			update_button_states(mon->tbar, 0);
		}
		
		case TrackAllMsg:
		{
			int i;
			
			break;
			for(i = 0; i < num_monitors; ++i)
			{
				struct Monitor *mon = monitors + i;
				update_monitor(mon);
			}
			
			break;
		}
			
		default:
			//if(msg == shell_hook_msg)
			//{
			//	handle_shell_msg(wp, lp);
			//}
			//else
				return DefWindowProc(wnd, msg, wp, lp);
	}
	return 0;
}



